package czStvDpds.movement;

import czStvDpds.collisionGeometry.*;
import czStvDpds.mapmanagment.Map;
import java.awt.geom.Line2D;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

/**
 * Poskytuje metody pro detekci kolizí mezi hráčem a mapu a mezi hráči
 *
 * @author Jiří Švejda
 */
public class CollisionManager {

    private static CollisionManager collisionManager;

    public static CollisionManager getCollisionManager() {
        if (collisionManager == null) {
            collisionManager = new CollisionManager();
        }
        return collisionManager;
    }

    CollisionManager() {
        players = new LinkedList<>();
    }
    private List<Player> players;

    public void addPlayer(Player player) {
        players.add(player);
    }

    public void removePlayer(Player player) {
        players.remove(player);
    }
    private Map map;

    public Map getMap() {
        return map;
    }

    public void setMap(Map map) {
        this.map = map;
    }
    private static final double PLAYER_WIDTH = 0.8;
    private static final double PLAYER_HEIGHT = 0.8;
    private static final double PLAYER_R = 0.65;

    /**
     * Zkontroluje, zda by hráč na daných souřadnicích kolidoval s mapou nebo
     * jinými hráči.
     *
     * @param newX
     * @param newY
     * @return
     * @deprecated Nepoužívat, zastaralé
     */
    public boolean isCollsion(double newX, double newY) {
        // Přepočíst na levý horní roh
        double realNewX = newX - PLAYER_WIDTH / 2;
        double realNewY = newY - PLAYER_HEIGHT / 2;

        // Hranice dlaždic na kterých hráč je
        int topLeftTileX = (int) realNewX;
        int topLeftTileY = (int) realNewY;
        int bottomRightTileX = (int) (realNewX + PLAYER_WIDTH);
        int bottomRightTileY = (int) (realNewY + PLAYER_HEIGHT);

        // Pokud je některá s dlaždic solid vrátit true
        for (int y = topLeftTileY; y <= bottomRightTileY; y++) {
            for (int x = topLeftTileX; x <= bottomRightTileX; x++) {
                if (map.getTile(x, y) != null) {
                    if (map.getTile(x, y).isSolid()) {
                        return true;
                    }
                }
            }
        }

        // Zkontrolovat, zda nekoliduje s nějakým jiným hráčem
        for (Player player2 : players) {

            double realPlayer2NewX = player2.getX() - PLAYER_WIDTH / 2;
            double realPlayer2NewY = player2.getY() - PLAYER_HEIGHT / 2;
            if (realPlayer2NewX > newX - PLAYER_WIDTH
                    && realPlayer2NewX < newX + PLAYER_WIDTH
                    && realPlayer2NewY > newY - PLAYER_HEIGHT
                    && realPlayer2NewY < newY + PLAYER_HEIGHT) {
                return true;
            }
        }

        // Jinak vrátí false
        return false;
    }

    /**
     * Lepší verze kolizí, zjistí na kterých dlaždicích/hráčích hráč stojí a ty
     * pak převede do různých geometrických tvarů a s těmi potom zjišťuje kolizi
     * (Budou správně fungovat barely, ploty, mříže atd). Zatím bude stále ve
     * 2D.
     *
     * Funguje správně, ještě ale nepodproju kolize mezi hráči.
     *
     * @param newX
     * @param newY
     * @return
     * @deprecated Zastaralé, nepoužívat
     */
    public boolean isShapeCollision(double newX, double newY) {
        // Přepočíst na levý horní roh
        double topLeftNewX = newX - PLAYER_WIDTH / 2; // Pro zjištění dlaždic na kterých hráč stojí se stále používá čtvercová forma
        double topLeftNewY = newY - PLAYER_HEIGHT / 2;

        // Hranice dlaždic na kterých hráč je
        int topLeftTileX = (int) topLeftNewX;
        int topLeftTileY = (int) topLeftNewY;
        int bottomRightTileX = (int) (topLeftNewX + PLAYER_WIDTH);
        int bottomRightTileY = (int) (topLeftNewY + PLAYER_HEIGHT);

        // Seznam hitboxů vytvořených z dlaždic na kterých hráč stojí
        List<SquareHitBox> collidingTileHitBoxes = new LinkedList<>();

        // Pokud je některá s dlaždic solid přidá její hitbox
        for (int y = topLeftTileY; y <= bottomRightTileY; y++) {
            for (int x = topLeftTileX; x <= bottomRightTileX; x++) {
                if (map.getTile(x, y) != null && map.getTile(x, y).isSolid()) {
                    List<SquareHitBox> shbes = HitBoxInitializer.getHitBoxInitializer().toSquareHitBox(map.getTile(x, y), x, y, map);
                    for (SquareHitBox shb : shbes) {
                        collidingTileHitBoxes.add(shb);
                    }
                }
            }
        }

        // Zkontrolovat kolizi mezi hráčem hitboxy dlaždic
        for (SquareHitBox tileHitBox : collidingTileHitBoxes) { // Ke hráčí se přistupuje jako ke kruhu
            if (newX > tileHitBox.getX() - PLAYER_R
                    && newX < tileHitBox.getX() + tileHitBox.getWidth() + PLAYER_R
                    && newY > tileHitBox.getY() - PLAYER_R
                    && newY < tileHitBox.getY() + tileHitBox.getHeight() + PLAYER_R) {
                return true;
            }
        }

        // Zkontrolovat kolizi mezi hráči - bude implementováno v lepší verzi kolizích níže

        // Jinak vrátí false
        return false;
    }

    /**
     * Ještě lepší verze kolizí, používá kruhy a polygony namísto kruhů a
     * čtverců. Stále ve 2D
     *
     * Ještě ale nepodproju kolize mezi hráči.
     *
     * @param newX
     * @param newY
     * @return
     */
    public boolean isBetterShapeCollision(double newX, double newY) {
        // CircleHitBox hráče
        CircleHitBox player = new CircleHitBox(newX, newY, PLAYER_R);

        // Dlaždice v hráčově okolí se převedou na polygonové hitboxy.
        // Pro jednoduchost to nejsou jen přesně ty na kterých stojí hitbox
        // hráče ale ty na kterých stojí čtvercový obrys hráče
        double topLeftNewX = newX - PLAYER_R;
        double topLeftNewY = newY - PLAYER_R;

        int topLeftTileX = (int) topLeftNewX;
        int topLeftTileY = (int) topLeftNewY;
        int bottomRightTileX = (int) (topLeftNewX + PLAYER_R * 2);
        int bottomRightTileY = (int) (topLeftNewY + PLAYER_R * 2);

        List<PolygonHitBox> collidingHitBoxes = new LinkedList<>();

        for (int y = topLeftTileY; y <= bottomRightTileY; y++) {
            for (int x = topLeftTileX; x <= bottomRightTileX; x++) {
                if (map.getTile(x, y) != null && map.getTile(x, y).isSolid()) {
                    List<PolygonHitBox> phbes = new LinkedList<>();
                    try {
                        phbes = HitBoxInitializer.getHitBoxInitializer().getPolygonHitBox(map.getTile(x, y), x, y, map);
                    } catch (IOException ex) {
                        System.out.println("Nepodarilo se nacist hitboxy ze souboru, kolize selhali");
                    }
                    for (PolygonHitBox phb : phbes) {
                        collidingHitBoxes.add(phb);
                    }
                }
            }
        }

        // Zkontrolovat kolizi mezi kruhovým hitboxem hráče a polygonovými
        // hitboxy dlaždic
        for (PolygonHitBox polygon : collidingHitBoxes) {
            if (polygon.contains(new PointHitBox(player.getX(), player.getY()))) {
                return true;
            }

            // Najít nebližší dva vrcholy ke středu kruhu
            List<PointHitBox> vertices = new LinkedList<>();
            for (int i = 0; i < polygon.getNumVertices(); i++) {
                vertices.add(polygon.getVertex(i));
            }

            // DEBUG
            for (PointHitBox vertex : vertices) {
                System.out.println("VERTEX X: " + vertex.getX());
                System.out.println("VERTEX Y: " + vertex.getY());
            }

            // Střed hráče
            PointHitBox playerCenter = new PointHitBox(player.getX(), player.getY());

            // Nejbližší vrchol
            PointHitBox closestVertex = null;
            for (PointHitBox vertex : vertices) {
                if (closestVertex != null) {
                    if (vertex.getDistance(playerCenter) < closestVertex.getDistance(playerCenter)) {
                        closestVertex = vertex;
                    }
                } else {
                    closestVertex = vertex;
                }
            }
            vertices.remove(closestVertex);

            // Druhý nejbližší vrchol
            PointHitBox secondClosestVertex = null;
            for (PointHitBox vertex : vertices) {
                if (secondClosestVertex != null) {
                    if (vertex.getDistance(playerCenter) < secondClosestVertex.getDistance(playerCenter)) {
                        secondClosestVertex = vertex;
                    }
                } else {
                    secondClosestVertex = vertex;
                }
            }
            vertices.remove(secondClosestVertex);

            // DEBUG
            System.out.println("closest ---> x: " + closestVertex.getX() + ", y: " + closestVertex.getY());
            System.out.println("secondClosest - x: " + secondClosestVertex.getX() + ", y: " + secondClosestVertex.getY());

            // Najít bod na přímce tvořené těmito body nejbližší ke středu hráče
            PointHitBox closestOnLine = getClosestPoint(closestVertex, secondClosestVertex, playerCenter);

            // Zjistit vzdálenost mezi tímto bodem a středem hráče
            double distance = closestOnLine.getDistance(playerCenter);
            System.out.println("DISTANCE: " + distance);

            // Vrátít true jestli je vzdálenost menší než poloměr hráče
            if (distance < PLAYER_R) {
                System.out.println("YES");
                return true;
            } else {
                System.out.println("NO");
            }
        }

        // Kolize mezi hráči
        // WIP

        // Jinak vrátí false
        return false;
    }

    /**
     * Vrátí nejbližší bod na přímce
     *
     * @param pt1
     * @param pt2
     * @param p
     * @return
     */
    private static PointHitBox getClosestPoint(PointHitBox pt1, PointHitBox pt2, PointHitBox p) {
        double u = ((p.x - pt1.x) * (pt2.x - pt1.x) + (p.y - pt1.y) * (pt2.y - pt1.y)) / (sqr(pt2.x - pt1.x) + sqr(pt2.y - pt1.y));
        if (u > 1.0) {
            return (PointHitBox) pt2;
        } else if (u <= 0.0) {
            return (PointHitBox) pt1;
        } else {
            return new PointHitBox((int) (pt2.x * u + pt1.x * (1.0 - u) + 0.5), (int) (pt2.y * u + pt1.y * (1.0 - u) + 0.5));
        }
    }

    private static double sqr(double x) {
        return x * x;
    }
    /**
     * Ke kulce je přistupováno jako ke čtverci.
     */
    private static final double BULLET_SIZE = 0.2;

    public boolean isBulletCollision(double newX, double newY) {
        // Kolize s mapou

        // Kulka (kruh)
        CircleHitBox bullet = new CircleHitBox(newY, newY, BULLET_SIZE);

        // Dlaždice v okolí kulky se převedou na polygonové hitboxy.
        // Pro jednoduchost to nejsou jen přesně ty na kterých je hitbox
        // kulky ale ty na kterých stojí čtvercový obrys kulky
        double topLeftNewX = newX - BULLET_SIZE;
        double topLeftNewY = newY - BULLET_SIZE;

        int topLeftTileX = (int) topLeftNewX;
        int topLeftTileY = (int) topLeftNewY;
        int bottomRightTileX = (int) (topLeftNewX + BULLET_SIZE * 2);
        int bottomRightTileY = (int) (topLeftNewY + BULLET_SIZE * 2);

        List<PolygonHitBox> collidingHitBoxes = new LinkedList<>();

        for (int y = topLeftTileY; y <= bottomRightTileY; y++) {
            for (int x = topLeftTileX; x <= bottomRightTileX; x++) {
                if (map.getTile(x, y) != null && map.getTile(x, y).isSolid()) {
                    List<PolygonHitBox> phbes = new LinkedList<>();
                    try {
                        phbes = HitBoxInitializer.getHitBoxInitializer().getPolygonHitBox(map.getTile(x, y), x, y, map);
                    } catch (IOException ex) {
                        System.out.println("Nepodarilo se nacist hitboxy ze souboru, kolize selhali");
                    }
                    for (PolygonHitBox phb : phbes) {
                        collidingHitBoxes.add(phb);
                    }
                }
            }
        }

        // Zkontrolovat kolize mezi hitboxy dlaždic a hitboxem kulky
        for (PolygonHitBox polygon : collidingHitBoxes) {
            if (polygon.contains(new PointHitBox(bullet.getX(), bullet.getY()))) {
                return true;
            }

            // Najít nebližší dva vrcholy ke středu kruhu
            List<PointHitBox> vertices = new LinkedList<>();
            for (int i = 0; i < polygon.getNumVertices(); i++) {
                vertices.add(polygon.getVertex(i));
            }

            // Střed hráče
            PointHitBox bulletCenter = new PointHitBox(bullet.getX(), bullet.getY());

            // Nejbližší vrchol
            PointHitBox closestVertex = null;
            for (PointHitBox vertex : vertices) {
                if (closestVertex != null) {
                    if (vertex.getDistance(bulletCenter) < closestVertex.getDistance(bulletCenter)) {
                        closestVertex = vertex;
                    }
                } else {
                    closestVertex = vertex;
                }
            }
            vertices.remove(closestVertex);

            // Druhý nejbližší vrchol
            PointHitBox secondClosestVertex = null;
            for (PointHitBox vertex : vertices) {
                if (secondClosestVertex != null) {
                    if (vertex.getDistance(bulletCenter) < secondClosestVertex.getDistance(bulletCenter)) {
                        secondClosestVertex = vertex;
                    }
                } else {
                    secondClosestVertex = vertex;
                }
            }
            vertices.remove(secondClosestVertex);

            // Najít bod na přímce tvořené těmito body nejbližší ke středu kulky
            PointHitBox closestOnLine = getClosestPoint(closestVertex, secondClosestVertex, bulletCenter);

            // Zjistit vzdálenost mezi tímto bodem a středem kulky
            double distance = closestOnLine.getDistance(bulletCenter);
            System.out.println("DISTANCE: " + distance);

            // Vrátít true jestli je vzdálenost menší než poloměr kulky
            if (distance < PLAYER_R) {
                return true;
            }
        }

        // Kolize s hráčem
        // WIP

        // Jinak vrátí false
        return false;
    }
}
